// <copyright file="DebugConsole.cs" company="TenneySoftware">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>Robert Tenney</author>
// <email>rob10e@yahoo.com</email>
// <date>April 2009</date>
// <summary>Debug console reads and processes the user input, then directs the commands to the appropriate module.</summary>

// License:

// Product: RPGRoguelikeEngine .Net
// Author: Robert N. Tenney
// Copyright: June 2009

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation version 2
// of the License.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// This license covers all source code, binaries, and other materials related to this project
// unless specifically stated otherwise.

using System;
using System.Collections.Generic;
using System.Text;

namespace TenneySoftware.Engine.Debug {
    /// <summary>
    /// Delegate for sending the processed command to the specified module.
    /// </summary>
    /// <param name="command">Command to send.</param>
    /// <returns>True if the module was able to process the command.</returns>
    public delegate bool RedirectedCommand(string command);

    /// <summary>
    /// Debug console reads and processes the user input, then directs the commands to the appropriate module.
    /// </summary>
    public class DebugConsole : IDebugConsole {
        /// <summary>
        /// Reference to the debug console.
        /// </summary>
        private static DebugConsole console;

        /// <summary>
        /// Buffer to hold the output messages.
        /// </summary>
        private OutputBufferCollection outputBuffer;

        /// <summary>
        /// Input string which uses the StringBuilder class.
        /// </summary>
        private StringBuilder inputString;

        /// <summary>
        /// Buffer to hold the input strings.
        /// </summary>
        private LinkedList<string> inputBuffer;

        /// <summary>
        /// Node to navigate the linked list of the input buffer.
        /// </summary>
        private LinkedListNode<string> inputBufferNode;

        /// <summary>
        /// Cursor position on the input line.
        /// </summary>
        private int position;

        /// <summary>
        /// Target (reference) of debug commands.
        /// </summary>
        private ObjectIdentifier target;

        /// <summary>
        /// Engine attached to this console.
        /// </summary>
        private RPGEngine engine;

        /// <summary>
        /// Debug buffer to hold the debug messages that are displayed on screen.
        /// </summary>
        private DebugBuffer bufferForDebug;

        /// <summary>
        /// Debug mode processor.
        /// </summary>
        private DebugModeProcessor processor;

        /// <summary>
        /// Dictionary of delegates that send the debug string to the specified modules.
        /// </summary>
        private Dictionary<string, RedirectedCommand> commandHandler;

        /// <summary>
        /// Initializes a new instance of the DebugConsole class.
        /// </summary>
        /// <param name="engine">Engine to attach to the console.</param>
        public DebugConsole(RPGEngine engine) {
            this.engine = engine;
            this.inputString = new StringBuilder();
            this.inputBuffer = new LinkedList<string>();
            this.outputBuffer = new OutputBufferCollection();
            this.commandHandler = new Dictionary<string, RedirectedCommand>();
            this.commandHandler.Add("VIDEO", null);
            this.commandHandler.Add("LEVELGENERATOR", null);
            this.commandHandler.Add("CONSOLE", null);
            this.commandHandler.Add("ENGINE", null);
            this.InitializeDebugProcessor();
        }

        /// <summary>
        /// Gets or sets a reference to the debug console.
        /// </summary>
        /// <value>
        /// A reference to the debug console.
        /// </value>
        public static DebugConsole Console {
            get {
                return console;
            }

            set {
                console = value;
            }
        }

        /// <summary>
        /// Gets or sets the debug buffer.
        /// </summary>
        /// <value>
        /// The debug buffer.
        /// </value>
        public DebugBuffer BufferForDebug {
            get {
                return this.bufferForDebug;
            }

            set {
                this.bufferForDebug = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to display the debug messages verbosly.
        /// </summary>
        /// <value>
        /// A value indicating whether to display the debug messages verbosly.
        /// </value>
        public bool Verbose {
            get {
                return this.Processor.DebugModes.GetValue("Verbose");
            }

            set {
                this.Processor.DebugModes.SetValue("Verbose", value);
            }
        }

        /// <summary>
        /// Gets the outbut buffer.
        /// </summary>
        /// <value>
        /// The outbut buffer.
        /// </value>
        public OutputBufferCollection OutputBuffer {
            get {
                return this.outputBuffer;
            }
        }

        /// <summary>
        /// Gets or sets the target (reference) of a debug command.
        /// </summary>
        /// <value>
        /// The target (reference) of a debug command.
        /// </value>
        public ObjectIdentifier Target {
            get {
                return this.target;
            }

            set {
                this.target = value;
            }
        }

        /// <summary>
        /// Gets the cursor position for the debug console input.
        /// </summary>
        /// <value>
        /// The cursor position for the debug console input.
        /// </value>
        public int Position {
            get {
                return this.position;
            }
        }

        /// <summary>
        /// Gets the debug console input string.
        /// </summary>
        /// <value>
        /// The debug console input string.
        /// </value>
        public string Input {
            get {
                return "> " + this.inputString.ToString();
            }
        }

        /// <summary>
        /// Gets or sets the DebugMode processor.
        /// </summary>
        /// <value>
        /// The DebugMode processor.
        /// </value>
        protected DebugModeProcessor Processor {
            get {
                return this.processor;
            }
            
            set {
                this.processor = value;
            }
        }

        /// <summary>
        /// Pass a character (char) to the debug console for string input.
        /// </summary>
        /// <param name="chr">Character to pass to debug console.</param>
        public void InputChar(char chr) {
            this.inputString.Insert(this.position, chr);
            this.position++;
        }

        /// <summary>
        /// Pass a integer key value not covered by char to the debug console for string input or manipulation.
        /// </summary>
        /// <param name="input">Key value.</param>
        public void InputOtherChar(int input) {
            switch (input) {
                case 8: // Backspace
                    if (this.position > 0) {
                        this.position--;
                        this.inputString.Remove(this.position, 1);
                    }

                    break;
                case 13: // Enter
                    this.inputBuffer.AddLast(this.inputString.ToString());
                    this.inputBufferNode = this.inputBuffer.Last;
                    this.outputBuffer.Add(this.Input + Environment.NewLine);
                    this.ParseInput();
                    this.inputString = new StringBuilder();
                    this.position = 0;
                    break;
                case 37: // Left Arrow
                    if (this.position != 0) {
                        this.position--;
                    }

                    break;
                case 38: // Up Arrow
                    if (this.inputBuffer.Count > 0) {
                        if (this.inputBufferNode != null) {
                            this.inputString = new StringBuilder(this.inputBufferNode.Value);
                            if (this.inputBufferNode.Previous != null) {
                                this.inputBufferNode = this.inputBufferNode.Previous;
                            } else {
                                this.inputBufferNode = this.inputBuffer.Last;
                            }
                        }
                    }

                    break;
                case 39: // Right Arrow
                    if (this.position != this.inputString.Length) {
                        this.position++;
                    }

                    break;
                case 40: // Down Arrow
                    if (this.inputBuffer.Count > 0) {
                        if (this.inputBufferNode != null) {
                            this.inputString = new StringBuilder(this.inputBufferNode.Value);
                            if (this.inputBufferNode.Next != null) {
                                this.inputBufferNode = this.inputBufferNode.Next;
                            } else {
                                this.inputBufferNode = this.inputBuffer.First;
                            }
                        }
                    }

                    break;
                case 46: // Delete
                    if (this.position != this.inputString.Length) {
                        this.inputString.Remove(this.position, 1);
                    }

                    break;
            }
        }

        /// <summary>
        /// Processes the command string sent by the console.
        /// </summary>
        /// <param name="command">String command to process.</param>
        /// <returns>True if command was handled by this module.</returns>
        public bool ConsoleCommand(string command) {
            bool results = this.Processor.ConsoleCommand(command);
            if (this.Processor.DebugModes.GetValue("Exit")) {
                Environment.Exit(0);
            }
            
            return results;
        }

        /// <summary>
        /// Initializes the debug mode processor.
        /// </summary>
        private void InitializeDebugProcessor() {
            DebugMode[] modes = new DebugMode[] {
                new DebugMode("Verbose", 0, "Verbose debug output."),
                new DebugMode("Exit", 0, "Exits from the game.")
            };

            this.processor = new DebugModeProcessor(modes, "Console");
        }

        /// <summary>
        /// Method that parses the input string entered in by the user and sends the commands to the appropriate modules.
        /// </summary>
        private void ParseInput() {
            bool sparseHelp = false;
            bool targetedModule = false;
            if (this.engine.GraphicsManager is IDebugConsole) {
                this.commandHandler["VIDEO"] = (this.engine.GraphicsManager as IDebugConsole).ConsoleCommand;
            }

            if (this.engine.LevelGenerator is IDebugConsole) {
                this.commandHandler["LEVELGENERATOR"] = (this.engine.LevelGenerator as IDebugConsole).ConsoleCommand;
            }

            this.commandHandler["CONSOLE"] = this.ConsoleCommand;
            this.commandHandler["ENGINE"] = this.engine.ConsoleCommand;
            bool handled = false;
            string command = this.inputString.ToString();
            string[] parsed = command.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
            RedirectedCommand comm = null;
            string commandToSend = string.Empty;
            if (parsed.GetLength(0) > 1) {
                try {
                    comm = this.commandHandler[parsed[0].ToUpperInvariant()];
                    commandToSend = parsed[1];
                    targetedModule = true;
                    handled = comm(commandToSend);
                } catch (KeyNotFoundException) {
                    this.OutputBuffer.Add("'" + parsed[0] + "' was not found as a valid module.");
                    return;
                }
            } else {
                comm = this.commandHandler["CONSOLE"];
                commandToSend = parsed[0];
            }

            commandToSend = commandToSend.Replace("?", "help");
            if (commandToSend.ToUpperInvariant() == "HELP") {
                sparseHelp = true;
            }

            if (!handled && targetedModule) {
                this.OutputBuffer.Add("Command: '" + commandToSend + "' was not found in the module '" + parsed[0] + "'");
            }

            if (!handled && !targetedModule) {
                bool found = false;
                foreach (KeyValuePair<string, RedirectedCommand> kvp in this.commandHandler) {
                    if (this.Verbose) {
                        this.OutputBuffer.Add("Checking module: '" + kvp.Key + "' for command: '" + commandToSend + "'\n");
                    }

                    comm = kvp.Value;
                    if (sparseHelp) {
                        commandToSend = "sparsehelp";
                    } else {
                        commandToSend = parsed[0];
                    }

                    try {
                        handled = comm(commandToSend);
                    } catch (NullReferenceException) {
                        handled = false;
                    }

                    if (handled && !sparseHelp) {
                        if (this.Verbose) {
                            this.OutputBuffer.Add("\nFound command in module: '" + kvp.Key + "'");
                        }

                        found = true;
                    } else {
                        if (this.Verbose) {
                            this.OutputBuffer.Add("\n'" + commandToSend + "' was not found in: '" + kvp.Key + "' module.");
                        }
                    }
                }

                if (!found && !sparseHelp) {
                    this.OutputBuffer.Add("Command '" + commandToSend + "' was not found in any modules.");
                }
            }
        }
    }
}
